﻿package fpc.controls.mutators;
import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.geom.Rectangle;
import fpc.AComponent;
import fpc.styles.skins.ButtonState;
import fpc.styles.skins.IScrollBarSkin;
import fpc.styles.skins.NullScrollBarSkin;
import fpc.styles.skins.SkinType;
import fpc.styles.Style;
import fpc.utils.ClassFactory;
import fpc.utils.Herald;
import fpc.utils.IFactory;
import haxe.Timer;

class ScrollBar extends AComponent
{
	public var heraldChange:Herald<ScrollBar>;
	
	var _horisontal:Bool;
	
	var _active:Bool;
	
	public function new(horisontal:Bool) 
	{
		heraldChange = new Herald();
		
		_horisontal = horisontal;
		_value = 0;
		_pageScrollSize = 0;
		_fullScrollSize = 0;
		_clickStep = 20;
		_thumbPortion = 1;
		_active = false;
		_enabled = true;
		clickInterval = 50;
		
		super();
		if (_horisontal)
		{
			width = 100;
		}
		else
		{			
			height = 100;
		}
		
		updateWheelListener();
	}
	
	var _sprite:Sprite;
	
	override function createBaseObject():DisplayObject
	{
		_sprite = new Sprite();
		return _sprite;
	}
	
	// SKINS
	
	var _skin:IScrollBarSkin;
	
	var _skinFactory:IFactory<IScrollBarSkin>;
	
	override function updateStyle()
	{
		var skinFactory = Style.findSkin(this, SKIN_TYPE_SCROLL_BAR);
		if (skinFactory != _skinFactory)
		{
			_skinFactory = skinFactory;
			
			var skin = skinFactory.create();
			applySkin(skin);
		}
	}
	
	function applySkin(skin:IScrollBarSkin)
	{
		if (_skin != null)
		{
			removeButtonsListeners();
			_sprite.removeChild(_skin.object);
		}
		
		_skin = skin;
		_skin.init(_horisontal);
		_skin.active = _active;
		_skin.thumbPortion = _thumbPortion;
		
		_sprite.addChild(skin.object);
		
		_needRenderEnabled = true;
		_needRenderSize = true;
		
		tryResize(_horisontal ? _width : _height);
		
		if (_enabled)
		{
			addButtonsListeners();
		}
		orderRender();
	}
	
	function onMouseWheel(event:MouseEvent)
	{
		if (updateValue(_value - event.delta * _clickStep))
		{
			moveThumbToValue();
		}
	}
	
	function addButtonsListeners()
	{
		var thumb = _skin.thumb;
		thumb.addEventListener(MouseEvent.MOUSE_DOWN, onThumbMouseDown);
		thumb.addEventListener(MouseEvent.MOUSE_OVER, onThumbMouseOver);
		thumb.addEventListener(MouseEvent.ROLL_OUT, onThumbRollOut);
		var left = _skin.left;
		left.addEventListener(MouseEvent.MOUSE_DOWN, onLeftMouseDown);
		left.addEventListener(MouseEvent.MOUSE_OVER, onLeftMouseOver);
		left.addEventListener(MouseEvent.ROLL_OUT, onLeftRollOut);
		var right = _skin.right;
		right.addEventListener(MouseEvent.MOUSE_DOWN, onRightMouseDown);
		right.addEventListener(MouseEvent.MOUSE_OVER, onRightMouseOver);
		right.addEventListener(MouseEvent.ROLL_OUT, onRightRollOut);
		var background = _skin.background;
		background.addEventListener(MouseEvent.MOUSE_DOWN, onBackgroundMouseDown);
	}
	
	function removeButtonsListeners()
	{
		var thumb = _skin.thumb;
		thumb.removeEventListener(MouseEvent.MOUSE_DOWN, onThumbMouseDown);
		thumb.removeEventListener(MouseEvent.MOUSE_OVER, onThumbMouseOver);
		thumb.removeEventListener(MouseEvent.ROLL_OUT, onThumbRollOut);
		var left = _skin.left;
		left.removeEventListener(MouseEvent.MOUSE_DOWN, onLeftMouseDown);
		left.removeEventListener(MouseEvent.MOUSE_OVER, onLeftMouseOver);
		left.removeEventListener(MouseEvent.ROLL_OUT, onLeftRollOut);
		var right = _skin.right;
		right.removeEventListener(MouseEvent.MOUSE_DOWN, onRightMouseDown);
		right.removeEventListener(MouseEvent.MOUSE_OVER, onRightMouseOver);
		right.removeEventListener(MouseEvent.ROLL_OUT, onRightRollOut);
		var background = _skin.background;
		background.addEventListener(MouseEvent.MOUSE_DOWN, onBackgroundMouseUp);
	}
	
	function updateWheelListener()
	{
		if (_enabled)
		{
			_sprite.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
		}
		else
		{
			_sprite.removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
		}
	}
	
	// BACKGROUND
	
	var _backgroundDownLeft:Bool;
	
	var _backgroundDownRight:Bool;
	
	function onBackgroundMouseDown(event:MouseEvent)
	{
		if (!_active)
		{
			return;
		}
		_sprite.stage.addEventListener(MouseEvent.MOUSE_UP, onBackgroundMouseUp);
		_skin.background.addEventListener(MouseEvent.ROLL_OUT, onBackgroundMouseUp);
		var isLeft = isMouseLeft();
		_skin.backgroundDownLeft = _backgroundDownLeft = isLeft;
		_skin.backgroundDownRight = _backgroundDownRight = !isLeft;
		downStart();
		orderRender();
	}
	
	function isMouseLeft()
	{
		return _horisontal
			? _sprite.mouseX < _skin.thumb.x
			: _sprite.mouseY < _skin.thumb.y;
	}
	
	function onBackgroundMouseUp(event:MouseEvent = null)
	{
		downStop();
		_sprite.stage.removeEventListener(MouseEvent.MOUSE_UP, onBackgroundMouseUp);
		_skin.background.removeEventListener(MouseEvent.ROLL_OUT, onBackgroundMouseUp);
		_skin.backgroundDownLeft = _backgroundDownLeft = false;
		_skin.backgroundDownRight = _backgroundDownRight = false;
		orderRender();
	}
	
	// END BACKGROUND
	
	// THUMB
	
	var _thumbOver:Bool;
	
	var _thumbDown:Bool;
	
	function onThumbMouseDown(event:MouseEvent)
	{
		_thumbDown = true;
		_skin.thumbState = DOWN;
		
		_skin.thumb.startDrag(_skin.thumbDragRect);
		var stage = _sprite.stage;
		stage.addEventListener(MouseEvent.MOUSE_UP, onThumbMouseUp);
		stage.addEventListener(MouseEvent.MOUSE_MOVE, onThumbMouseMove);
		orderRender();
	}
	
	function onThumbMouseOver(event:MouseEvent)
	{
		_thumbOver = true;
		_skin.thumbState = _thumbDown ? DOWN : OVER;
		
		orderRender();
	}
	
	function onThumbRollOut(event:MouseEvent)
	{
		_thumbOver = false;
		_skin.thumbState = _thumbDown ? DOWN : NORMAL;
		
		orderRender();
	}
	
	function onThumbMouseUp(event:MouseEvent)
	{
		_thumbDown = false;
		var stage = _sprite.stage;
		stage.removeEventListener(MouseEvent.MOUSE_UP, onThumbMouseUp);
		stage.removeEventListener(MouseEvent.MOUSE_MOVE, onThumbMouseMove);
		_skin.thumb.stopDrag();
		_skin.thumbState = _thumbOver ? OVER : NORMAL;
		orderRender();
	}
	
	function onThumbMouseMove(event:MouseEvent)
	{
		var rect = _skin.thumbDragRect;
		value = _horisontal
			? (_fullScrollSize - _pageScrollSize) * (_skin.thumb.x - rect.left) / (rect.right - rect.left)
			: (_fullScrollSize - _pageScrollSize) * (_skin.thumb.y - rect.top) / (rect.bottom - rect.top);
	}
	
	function moveThumbToValue()
	{
		var rect = _skin.thumbDragRect;
		var thumb = _skin.thumb;
		if (_horisontal)
		{
			thumb.x = Std.int(rect.left + value * (rect.right - rect.left) / (_fullScrollSize - _pageScrollSize));
		}
		else
		{
			thumb.y = Std.int(rect.top + value * (rect.bottom - rect.top) / (_fullScrollSize - _pageScrollSize));
		}
	}
	
	// END THUMB
	
	var _downCounter:Int;
	
	var _downTimer:Timer;
	
	function downStart()
	{
		_downCounter = 0;
		if (_downTimer == null)
		{
			_downTimer = new Timer(clickInterval);
		}
		_downTimer.run = onDownTimer;
	}
	
	function onDownTimer()
	{
		_downCounter++;
		if (_leftDown)
		{
			if (updateValue(_value - _clickStep))
			{
				moveThumbToValue();
			}
		}
		if (_rightDown)
		{
			if (updateValue(_value + _clickStep))
			{
				moveThumbToValue();
			}
		}
		if (_backgroundDownLeft)
		{
			if (updateValue(_value - lineClickStep))
			{
				moveThumbToValue();
				if (!isMouseLeft())
				{
					onBackgroundMouseUp();
				}
				orderRender();
			}
		}
		if (_backgroundDownRight)
		{
			if (updateValue(_value + lineClickStep))
			{
				moveThumbToValue();
				if (isMouseLeft())
				{
					onBackgroundMouseUp();
				}
				orderRender();
			}
		}
	}
	
	function downStop()
	{
		_downTimer.stop();
		_downTimer = null;
		
		if (_downCounter == 0)
		{
			onDownTimer();
		}
	}
	
	// LEFT
	
	var _leftOver:Bool;
	
	var _leftDown:Bool;
	
	function onLeftMouseDown(event:MouseEvent)
	{
		_leftDown = true;
		_skin.leftState = DOWN;
		
		_sprite.stage.addEventListener(MouseEvent.MOUSE_UP, onLeftMouseUp);
		orderRender();
		
		downStart();
	}
	
	function onLeftMouseOver(event:MouseEvent)
	{
		_leftOver = true;
		_skin.leftState = _leftDown ? DOWN : OVER;
		
		orderRender();
	}
	
	function onLeftRollOut(event:MouseEvent)
	{
		_leftOver = false;
		_skin.leftState = _leftDown ? DOWN : NORMAL;
		
		orderRender();
	}
	
	function onLeftMouseUp(event:MouseEvent)
	{
		downStop();
		
		_leftDown = false;
		_sprite.stage.removeEventListener(MouseEvent.MOUSE_UP, onLeftMouseUp);
		_skin.leftState = _leftOver ? OVER : NORMAL;
		orderRender();
	}
	
	// END LEFT
	
	// RIGHT
	
	var _rightOver:Bool;
	
	var _rightDown:Bool;
	
	function onRightMouseDown(event:MouseEvent)
	{
		_rightDown = true;
		_skin.rightState = DOWN;
		
		_sprite.stage.addEventListener(MouseEvent.MOUSE_UP, onRightMouseUp);
		orderRender();
		
		downStart();
	}
	
	function onRightMouseOver(event:MouseEvent)
	{
		_rightOver = true;
		_skin.rightState = _rightDown ? DOWN : OVER;
		
		orderRender();
	}
	
	function onRightRollOut(event:MouseEvent)
	{
		_rightOver = false;
		_skin.rightState = _rightDown ? DOWN : NORMAL;
		
		orderRender();
	}
	
	function onRightMouseUp(event:MouseEvent)
	{
		downStop();
		_rightDown = false;
		_sprite.stage.removeEventListener(MouseEvent.MOUSE_UP, onRightMouseUp);
		_skin.rightState = _rightOver ? OVER : NORMAL;
		orderRender();
	}
	
	// END RIGHT
	
	public static var SKIN_TYPE_SCROLL_BAR = new SkinType(
		new ClassFactory<IScrollBarSkin>(NullScrollBarSkin)
	);
	
	// LAYOUT
	
	var _needRenderSize:Bool;
	
	var _needRenderEnabled:Bool;
	
	override function render()
	{
		super.render();
		_skin.size = Std.int(_horisontal ? _width : _height);
		if (_needRenderEnabled)
		{
			if (_enabled)
			{
				if (_skin.thumbState == DISABLED)
				{
					_skin.thumbState = NORMAL;
				}
				if (_skin.leftState == DISABLED)
				{
					_skin.thumbState = NORMAL;
				}
				if (_skin.rightState == DISABLED)
				{
					_skin.rightState = NORMAL;
				}
				
				addButtonsListeners();
			}
			else
			{
				_skin.thumbState = DISABLED;
				_skin.leftState = DISABLED;
				_skin.rightState = DISABLED;
				
				removeButtonsListeners();
			}
			updateWheelListener();
			_needRenderEnabled = false;
		}
		_skin.update();
		if (_needRenderSize)
		{
			moveThumbToValue();
			_needRenderSize = false;
		}
	}
	
	function tryResize(newSize:Float)
	{
		if (newSize < _skin.minSize)
		{
			newSize = _skin.minSize;
		}
		var newWidth:Float;
		var newHeight:Float;
		if (_horisontal)
		{
			newWidth = newSize;
			newHeight = _skin.thickness;
		}
		else
		{
			newWidth = _skin.thickness;
			newHeight = newSize;
		}
		if (Std.int(newWidth) != Std.int(_width) || Std.int(newHeight) != Std.int(_height))
		{
			_width = newWidth;
			_height = newHeight;
			_needRenderSize = true;
			orderRender();
			heraldResize.dispatch(this);
		}
		else
		{
			_width = newWidth;
			_height = newHeight;
		}
	}
	
	override function setWidth(value)
	{
		if (_horisontal)
		{
			tryResize(value);
		}
		return _width;
	}
	
	override function setHeight(value)
	{
		if (!_horisontal)
		{
			tryResize(value);
		}
		return _height;
	}
	
	// PROPERTIES
	
	public var clickInterval:Int;
	
	public var enabled(getEnabled, setEnabled):Bool;
	var _enabled:Bool;
	function getEnabled()
	{
		return _enabled;
	}
	function setEnabled(value)
	{
		if (value == _enabled)
		{
			return value;
		}
		
		_enabled = value;
		_needRenderEnabled = true;
		orderRender();
		return value;
	}
	
	public var lineClickStep(getLineClickStep, setLineClickStep):Float;
	var _lineClickStep:Float;
	function getLineClickStep()
	{
		return Math.isNaN(_lineClickStep) || _lineClickStep <= 0 ? _pageScrollSize : _lineClickStep;
	}
	function setLineClickStep(value)
	{
		_lineClickStep = value;
		return _lineClickStep;
	}
	
	public var clickStep(getClickStep, setClickStep):Float;
	var _clickStep:Float;
	function getClickStep()
	{
		return _clickStep;
	}
	function setClickStep(value)
	{
		_clickStep = value;
		return _clickStep;
	}
	
	var _thumbPortion:Float;
	
	function calculateThumbPortion()
	{
		_active = _fullScrollSize > _pageScrollSize && _fullScrollSize > 0;
		if (_active)
		{
			_thumbPortion = _pageScrollSize / _fullScrollSize;
		}
		else
		{
			_thumbPortion = 1;
		}
		_skin.thumbPortion = _thumbPortion;
		_skin.active = _active;
		if (_value > _fullScrollSize - _pageScrollSize)
		{
			_value = _fullScrollSize - _pageScrollSize;
			if (_value < 0)
			{
				_value = 0;
			}
			updateValue(_value);
		}
		orderRender();
	}
	
	public var fullScrollSize(getFullScrollSize, setFullScrollSize):Float;
	var _fullScrollSize:Float;
	function getFullScrollSize()
	{
		return _fullScrollSize;
	}
	function setFullScrollSize(value)
	{
		_fullScrollSize = value;
		calculateThumbPortion();
		return _fullScrollSize;
	}
	
	public var pageScrollSize(getPageScrollSize, setPageScrollSize):Float;
	var _pageScrollSize:Float;
	function getPageScrollSize()
	{
		return _pageScrollSize;
	}
	function setPageScrollSize(value)
	{
		_pageScrollSize = value;
		calculateThumbPortion();
		return _pageScrollSize;
	}
	
	public var value(getValue, setValue):Float;
	var _value:Float;
	function getValue()
	{
		return _value;
	}
	function setValue(value:Float)
	{
		if (updateValue(value))
		{
			moveThumbToValue();
			orderRender();
		}
		return _value;
	}
	
	function updateValue(value:Float)
	{
		if (value < 0)
		{
			value = 0;
		}
		else
		{
			var delta = _fullScrollSize - _pageScrollSize;
			if (delta < 0)
			{
				value = 0;
			}
			else if (value > delta)
			{
				value = delta;
			}
		}
		if (_value != value)
		{
			_value = value;
			heraldChange.dispatch(this);
			return true;
		}
		return false;
	}
}